#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/scrnsaver.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <glib.h>
#include <dbus/dbus-glib.h>
#include "sgio.h"
#include "acpid.h"
#include "ud_socket.h"

Display *display=NULL;
FILE *brightnessFd;

typedef struct	{
	char	*key;
	char	*value;
} KCONFIG;

KCONFIG pm_on[]={
{"/proc/sys/vm/laptop_mode", "2"},
{"/proc/sys/vm/dirty_writeback_centisecs", "36000"},
{"/proc/sys/vm/dirty_expire_centisecs", "36000"},
{"/proc/sys/vm/swappiness", "10"}, 
{"/proc/sys/vm/dirty_ratio", "95"},
{"/proc/sys/vm/dirty_background_ratio", "1"},
{"/sys/class/scsi_host/host0/link_power_management_policy", "min_power\n"},
{"/sys/module/pcie_aspm/parameters/policy", "powersave"},
{"/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "ondemand"},
{"/sys/block/sda/queue/read_ahead_kb", "4000"},
{"/sys/devices/system/cpu/sched_smt_power_savings", "1"},
{NULL,NULL}
};

KCONFIG pm_off[]={
{"/proc/sys/vm/laptop_mode", "0"},
{"/proc/sys/vm/dirty_writeback_centisecs", "500"},
{"/proc/sys/vm/dirty_expire_centisecs", "3000"},
{"/proc/sys/vm/swappiness", "60"}, 
{"/proc/sys/vm/dirty_ratio", "40"},
{"/proc/sys/vm/dirty_background_ratio", "10"},
{"/sys/class/scsi_host/host0/link_power_management_policy", "max_performance\n"},
{"/sys/module/pcie_aspm/parameters/policy", "performance"},
{"/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "ondemand"},
{"/sys/block/sda/queue/read_ahead_kb", "128"},
{"/sys/devices/system/cpu/sched_smt_power_savings", "0"},
{NULL,NULL}
};

	int old_lm_state=-1;
	int lm_state;
	int old_idle=-1,idle=0;
	int idletime=4;
	int it;
	int disk;

void kget(char *, char *);
void kset(char *, char *);
void ac_on(void);
void ac_off(void);

static int handle_cmdline(int *argc, char ***argv);
static char *read_line(int fd);

static const char *progname;
static const char *socketfile = ACPI_SOCKETFILE;
static int max_events;

static void
time_expired(int signum)
{
	exit(EXIT_SUCCESS);
}
int acpi_fd;

struct timeval timeout;

DBusGConnection *dbus_sys_connection=NULL;
DBusGProxy *nm_proxy=NULL;

void dbus_init(void)	{
	GError *error=NULL;
	if(dbus_sys_connection) return;
	dbus_sys_connection=dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
	if(dbus_sys_connection==NULL)	{
		g_error_free(error);
		return;
	}
	nm_proxy = dbus_g_proxy_new_for_name (dbus_sys_connection,
                                   "org.freedesktop.NetworkManager",
                                   "/org/freedesktop/NetworkManager",
                                   "org.freedesktop.NetworkManager");
	
	if(nm_proxy==NULL)	{
		printf("error con proxy");
		return;
	}                                   
}

void nm_sleep(void)	{
	if(!dbus_sys_connection) return;
	GError *error;
	if (!dbus_g_proxy_call (nm_proxy, "sleep", &error, G_TYPE_INVALID, G_TYPE_INVALID))
	{
		printf("Error durmiendo");
		g_error_free(error);
	}

}

void nm_wake(void)	{
	GError *error;
	if(!dbus_sys_connection) return;
	if (!dbus_g_proxy_call (nm_proxy, "wake", &error, G_TYPE_INVALID, G_TYPE_INVALID))
	{
		printf("Error durmiendo");
		g_error_free(error);
	}

}

int acpi_init(void)	{
	int opts;
	
	/* open the socket */
	acpi_fd = ud_connect(socketfile);
	if (acpi_fd < 0) {
		fprintf(stderr, "can't open socket %s: %s\n",
			socketfile, strerror(errno));
		exit(EXIT_FAILURE);
	}
	fcntl(acpi_fd, F_SETFD, FD_CLOEXEC);
	

	opts = fcntl(acpi_fd,F_GETFL);
	if (opts < 0) {
		perror("fcntl(F_GETFL)");
		exit(EXIT_FAILURE);
	}
	opts = (opts | O_NONBLOCK);
	if (fcntl(acpi_fd,F_SETFL,opts) < 0) {
		perror("fcntl(F_SETFL)");
		exit(EXIT_FAILURE);
	}

	timeout.tv_sec=0;
	timeout.tv_usec=0;
}
void suspend(char *method)	{ 
				time_t t1,t2;
				int charge1, charge2;
				char buffer[512];
				system("xrandr --output LVDS --set BACKLIGHT 0");
				//system("xset dpms force suspend");
				system("vbetool dpms off");
				//setBrightness(0);
				//system("xrandr --output LVDS --off");
				
				//system("date; cat /proc/acpi/battery/BAT1/state");
				//kget(buffer, "/sys/class/power_supply/BAT1/charge_now");
				//sscanf(buffer, "%d", &charge1);
				
				nm_sleep();
				//system("dbus-send --system --type=method_call --dest=org.freedesktop.NetworkManager /org/freedesktop/NetworkManager org.freedesktop.NetworkManager.sleep");
				//dbus_system_call("org.freedesktop.NetworkManager", "org.freedesktop.NetworkManager.sleep");
				sleep(3);
				time(&t1);
				kset("/sys/power/state", method);
				time(&t2);
				nm_wake();
				//system("dbus-send --system --type=method_call --dest=org.freedesktop.NetworkManager /org/freedesktop/NetworkManager org.freedesktop.NetworkManager.wake");
				system("vbsetool dpms on");
				//kget(buffer, "/sys/class/power_supply/BAT1/charge_now");
				//sscanf(buffer, "%d", &charge2);
				//system("date; cat /proc/acpi/battery/BAT1/state");
				//system("xrandr dpms force on");
				old_lm_state=-1;
				system("xrandr --output LVDS --auto --set BACKLIGHT 0");
				sprintf(buffer, "/usr/bin/notify-send \"Sistema Despierto\" \"Ha estado durmiendo %li segundos\"", (int)t2-t1);
				// %.2f mAh\"", (int)t2-t1, (charge2-charge1)/1000.0);
				system(buffer);

				sleep(3);


				if(is_ac_adaptor_offline())	ac_off();
				else ac_on();
}

int acpi_run(void)	{
		char *event;
		int ret;
		fd_set l;
				
		if(!acpi_fd) return 0;
		
		FD_ZERO(&l);
		FD_SET(acpi_fd, &l);
		if(select(acpi_fd+1, &l, (fd_set*)NULL, (fd_set*)NULL, &timeout)<=0)
			return 0;
		/* read and handle an event */
		event = read_line(acpi_fd);
		if (event) {
			fprintf(stdout, "%s\n", event);
			if(!strncmp(event, "button/power", 12)) {
				suspend("mem");
			} else if (!strcmp(event, "ac_adapter ADP1 00000080 00000001"))	{
				ac_on();
			} else if (!strcmp(event, "ac_adapter ADP1 00000080 00000000")) {
				ac_off();
			} else if (!strncmp(event, "button/sleep", 12)) {
				suspend("disk");
			}
		} else if (errno == EPIPE) {
			fprintf(stderr, "connection closed\n");
			return 0;
		} else {
			static int nerrs;
			if (++nerrs >= ACPI_MAX_ERRS) {
				fprintf(stderr, "too many errors - aborting\n");
				ret = 1;
				return 0;
			}
		}
	return 1;
	}

int is_ac_adaptor_offline(void)	{
	FILE *f;
	int l;
	char buffer[512];
	char *b;
	kget(buffer, "/sys/class/power_supply/ADP1/online");
	return (buffer[0]=='0' ? 1:0);
	
	
}
/*
static XRRScreenResources  *xrr_res;

void xrr_init(void)	{
    xrr_res = XRRGetScreenResources (display, root);
    if (!res) fatal ("could not get screen resources");
}

void xrr_disable(void)	{
	
	xrrXRRSetCrtcConfig (display, xrr_res, 0x3c, CurrentTime,
			     0, 0, None, RR_Rotate_0, NULL, 0);
}
*/

/*
 * Get IDLE time
 * Taken from GAJIM source, src/common/idle.c
 *
 * Modified to return -1 when the screen saver is deactivated
 */

int getIdleTime() { /*{{{*/
	static XScreenSaverInfo *mit_info = NULL;
	int idle_time, event_base, error_base;

	if (XScreenSaverQueryExtension(display, &event_base, &error_base)) {
		if (mit_info == NULL) {
			mit_info = XScreenSaverAllocInfo();
		}
		XScreenSaverQueryInfo(display, RootWindow(display, 0), mit_info);

		if(mit_info->state == 3) { /* ScreenSaverDisabled */
			idle_time = -1;
		}
		else {
			idle_time = (mit_info->idle) / 1000;
		}
	}
	else {
		idle_time = 0;
	}
	return idle_time;
} /*}}}*/

void kget(char *buffer, char *path) {
	FILE *f;
	f=fopen(path, "rt");
	fgets(buffer, 256, f);
}
void kset(char *path, char *value)	{
	FILE *f;
	f=fopen(path, "wt");
	fputs(value, f);
	fflush(f);
	fclose(f);
}

void ksetlist(KCONFIG *c)	{
	int i;
	for(i=0;c[i].key;i++)	{
		kset(c[i].key, c[i].value);
	}
}

void setBrightness(int level) { /*{{{*/
	char output[255];
	int i, plevel;
	int saved;

	sprintf(output, "%i\n", level);
	if(fputs(output, brightnessFd) == EOF) {
		error("Failed to update brightness.");
	}
	fflush(brightnessFd); 
	//sprintf(output, "xrandr --output LVDS --set BACKLIGHT %i", level);
	//system(output);
}

int set_standby(int fd, int time)	{
	__u8 args[4] = {ATA_OP_SETIDLE1,time,0,0};
	if (do_drive_cmd(fd, args)) {
		perror(" HDIO_DRIVE_CMD(setidle1) failed");
		return 0;
	}
	return 1;
}
int set_apmmode(int fd, int apmmode)	{
		__u8 args[4] = {ATA_OP_SETFEATURES,0,0,0};
		if (apmmode==255) {
			/* disable Advanced Power Management */
			args[2] = 0x85; /* feature register */
		} else {
			/* set Advanced Power Management mode */
			args[2] = 0x05; /* feature register */
			args[1] = apmmode; /* sector count register */
		}
		if (do_drive_cmd(fd, args)) {
			//err = errno;
			perror(" HDIO_DRIVE_CMD failed");
			return 0;
		}
	return 1;
}
#define MAX_BUFLEN	1024
static char *
read_line(int fd)
{
	static char *buf;
	int buflen = 64;
	int i = 0;
	int r;
	int searching = 1;

	while (searching) {
		buf = realloc(buf, buflen);
		if (!buf) {
			fprintf(stderr, "ERR: malloc(%d): %s\n",
				buflen, strerror(errno));
			return NULL;
		}
		memset(buf+i, 0, buflen-i);

		while (i < buflen) {
			r = read(fd, buf+i, 1);
			if (r < 0 && errno != EINTR) {
				/* we should do something with the data */
				fprintf(stderr, "ERR: read(): %s\n",
					strerror(errno));
				return NULL;
			} else if (r == 0) {
				/* signal this in an almost standard way */
				errno = EPIPE;
				return NULL;
			} else if (r == 1) {
				/* scan for a newline */
				if (buf[i] == '\n') {
					searching = 0;
					buf[i] = '\0';
					break;
				}
				i++;
			}
		}
		if (buflen >= MAX_BUFLEN) {
			break;
		}
		buflen *= 2;
	}

	return buf;
}

void notify_send(char *title, char *body, char *cat, char *icon)	{
	char b[512];
	sprintf(b, "/usr/bin/notify-send -i %s -c %s \"%s\" \"%s\"", icon, cat, title, body);
	system(b);
}

void ac_on(void)	{
	notify_send("Connected to AC Power", "You are working on high performance mode", "battery", "battery");
	//system("/usr/bin/notify-send \"Modo batería desactivado\" \"Vamos a aprovechar que está enchufado a la corriente y le subimos el brillo para que luzca\"");
	disk=open("/dev/sda", O_RDWR|O_NONBLOCK);
	setBrightness(6);
	ksetlist(pm_off);
				
	set_apmmode(disk, 255);
	set_standby(disk, 60*5);
	system("iwconfig wlan0 power off");
	//system("iwconfig wlan0 rate auto");

	close(disk);
}
void ac_off(void)	{
	notify_send("Disconnected AC Power", "You are working on power-save mode", "battery", "battery");
	//system("/usr/bin/notify-send \"Modo batería activado\" \"Ahorrar energía a cascoporro para que cunda.\"");
	disk=open("/dev/sda", O_RDWR|O_NONBLOCK);

	setBrightness(0);
	ksetlist(pm_on);
				
	set_apmmode(disk, 1);
	set_standby(disk, 60*1);
				
	system("iwconfig wlan0 power on");
	//system("iwconfig wlan0 rate 0");
}
int main(void)	{
	brightnessFd = fopen("/sys/class/backlight/acpi_video0/brightness", "w");
	acpi_init();
	if(is_ac_adaptor_offline())	ac_off();
	else ac_on();
	g_type_init();
	while(1)	{
		/*
		lm_state=is_ac_adaptor_offline();
		if(lm_state!=old_lm_state) {
			//system(laptop_mode);
			old_lm_state=lm_state;
			

			if(lm_state)	{
				ac_off();
			} else {
				ac_on();
			}
			
			
			idle=1;
		}
		
		if(lm_state) {
			it=getIdleTime();
			if(!idle)	{
				if(it>=idletime)	{
					idle=1;
					printf("IDLE\n");
					system("xrandr --output LVDS --off");
					setBrightness(0);
				} 
			} else {
				if(it<idletime)	{
					idle=0;
					printf("END IDLE\n");
					system("xrandr --output LVDS --auto --set BACKLIGHT 0");
					//setBrightness(1);
				}
			}
		*/
	sleep(1);
	dbus_init();
	if (!display)	{
		display=XOpenDisplay(":0");
	}
	while(acpi_run());
	}
	fclose(brightnessFd);
	return 0;
}
